﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

using TDB;
using TeamDev.FluentProgramming;
using TDB.LinqSerializer;

namespace TDB.DBInternals
{
  /// <summary>
  /// AssemblyStore stores assemblies of database savable object types
  /// Before you can save an object into the DB you have to submit it's assembly to 
  /// inform the DB of the object definition. 
  /// 
  /// The DB will use the assembly to manage the object in the correct way without doing strange operations to access it's data. 
  /// This implementation is .Net specific. 
  /// 
  /// Every time an assembly is submitted to the store, it's analyzed to retrieve informations about object's strucures
  /// and to intercept Store Procedure's or Indexes fo the database that will be used as object's business logic. 
  /// </summary>
  public static class AssemblyStore
  {
    private static Dictionary<Type, Type> _storeabletypes = new Dictionary<Type, Type>();
    private static Dictionary<Type, Type> _indexabletypes = new Dictionary<Type, Type>();
    private static Dictionary<string, Type> _fulltypesearch = new Dictionary<string, Type>();
    private static bool _init = false;

    static AssemblyStore()
    {
      SerializationExtensions.TypeResolve = (tn) => GetType(tn);
    }

    public static Type GetType(string typename)
    {
      Type result = null;
      if (_fulltypesearch.ContainsKey(typename))
        return _fulltypesearch[typename];

      result = _storeabletypes.Values.Where(t => t.AssemblyQualifiedName == typename).FirstOrDefault();
      if (result == null)
        result = _storeabletypes.Values.Where(t => t.FullName == typename).FirstOrDefault();
      if (result == null)
        result = _storeabletypes.Values.Where(t => t.Name == typename).FirstOrDefault();

      return result;
    }

    public static void Set(byte[] assembly)
    {
      string assdir = Path.Combine(Configuration.Current.DataBasePath, "Assemblies");
      if (!Directory.Exists(assdir))
        Directory.CreateDirectory(assdir);

      try
      {
        var ass = Assembly.Load(assembly);
        string filename = Path.Combine(assdir, ass.FullName);
        File.WriteAllBytes(filename, assembly);

        CreateTypeIndex(ass.GetTypes());
      }
      catch (Exception ex)
      {
        TeamDev.SDK.EventLogWriter.LogMessage(string.Format("Error accepting external assembly: {0}", ex.Message), System.Diagnostics.EventLogEntryType.Warning);
      }
    }

    private static void CreateTypeIndex(IEnumerable<Type> types)
    {
      types.ForAll(type => CreateTypeIndex(type));
    }

    private static void CreateTypeIndex(Type tt)
    {
      tt.GetInterfaces().ForAll(
        interf =>
        {
          if (typeof(ITypeStore).IsAssignableFrom(interf))
            interf.GetGenericArguments().ForAll(iftype =>
              {
                if (_storeabletypes.ContainsKey(iftype))
                  _storeabletypes[iftype] = tt;
                else
                  _storeabletypes.Add(iftype, tt);

                if (_fulltypesearch.ContainsKey(tt.FullName))
                  _fulltypesearch[tt.FullName] = tt;
                else
                  _fulltypesearch.Add(tt.FullName, tt);

              });

          if (typeof(ITypeTrigger).IsAssignableFrom(interf))
            interf.GetGenericArguments().ForAll(iftype =>
              {
                if (_indexabletypes.ContainsKey(iftype))
                  _indexabletypes[iftype] = tt;
                else
                  _indexabletypes.Add(iftype, tt);
              });
        });
    }

    public static Assembly Get(string fullname)
    {
      string assdir = Path.Combine(Configuration.Current.DataBasePath, "Assemblies");
      string filename = Path.Combine(assdir, fullname);

      if (File.Exists(filename))
      {
        var ass = Assembly.LoadFile(filename);
        if (ass != null)
        {
          CreateTypeIndex(ass.GetTypes());
        }
        return ass;
      }
      return null;
    }

    public static IEnumerable<Type> GetKnowTypes(ICustomAttributeProvider provider)
    {
      if (!_init)
      {
        string assdir = Path.Combine(Configuration.Current.DataBasePath, "Assemblies");
        if (Directory.Exists(assdir))
          Directory.GetFiles(assdir).ForAll(filename => Get(filename));
        _init = true;
      }
      return _storeabletypes.Keys;
    }
  }
}
